package se.nikmel.AnDisper;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import se.nikmel.AnDisper.DisperServer.DisperInterface;
import se.nikmel.AnDisper.DisperServer.IconCache;
import se.nikmel.AnDisper.DisperServer.ServerInfo;
import se.nikmel.AnDisper.UI.I_UI_ViewController;
import se.nikmel.AnDisper.UI.OnDisperConnectionError;
import se.nikmel.AnDisper.UI.UI_AppLauncher_Controller;
import se.nikmel.AnDisper.UI.UI_DisplayConfig_Controller;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Menu;
import android.view.MenuItem;
import android.view.TouchDelegate;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.Window;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Toast;
import android.widget.ViewFlipper;

public class MainActivity extends Activity implements OnDisperConnectionError {

	private static final String TAG = "MainActivity"; // Logging tag
	// Setting keys in the Bundle
	public static final String SETTING_SEEN_SERVERS = "SeenServers";
	// Static variable used to exchange info with other activities (e.g.
	// ServerSetup)
	public static Map<String, ServerInfo> m_mapSeenServers;

	// UI Elements -- Note! Main activity does not have any layout in res. The
	// screen is created entirely in code
	private _MyFlipper m_rootFlipper;
	private TouchDelegate m_oTouchDelegate; // To redirect all touch events to the flipper 
	private UI_DisplayConfig_Controller m_uiDisplyConfig;
	private UI_AppLauncher_Controller m_uiApplications;
	// Other
	private _ConnectToServerTask m_tskConnect = null;
	private DisperInterface m_oDisperServer;

	/*
	 * 
	 * ************ ACTIVITY OVERRIDES **********
	 */

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// 1. Get settings
		try {
			loadServerList();
		} catch (Exception e) {
			Log.e(TAG, "(EE) Failed to load seen servers!", e);
		}

		// 2. Set content
		this.requestWindowFeature(Window.FEATURE_PROGRESS);
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);

		// 3. Build UI
		m_rootFlipper = new _MyFlipper(this);
		this.setContentView(m_rootFlipper);
		m_uiApplications = new UI_AppLauncher_Controller(this);
		m_uiApplications.setOnConectionErrorListener(this);
		m_uiDisplyConfig = new UI_DisplayConfig_Controller(this);
		m_uiDisplyConfig.setOnConectionErrorListener(this);
		m_rootFlipper.addView(m_uiDisplyConfig.getView());
		m_rootFlipper.addView(m_uiApplications.getView());
		
		Rect rctMe = new Rect();
		m_uiApplications.getView().getHitRect(rctMe);	
		m_oTouchDelegate = new TouchDelegate(rctMe, m_rootFlipper);
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (m_oDisperServer == null) {
			connectToServer(findLastSeenSeverIP());
		} else
			// Resume current view
			getCurrentView().resume();
	}

	private String findLastSeenSeverIP() {
		Map<String, ServerInfo> mapSeenServers = MainActivity.m_mapSeenServers;
		String sIP = null;
		Date dteLastSeen = null;

		for (ServerInfo oServer : mapSeenServers.values()) {
			if (dteLastSeen == null) {
				sIP = oServer.getIP();
				dteLastSeen = oServer.getLastSeen();
			} else if (dteLastSeen.before(oServer.getLastSeen())) {
				sIP = oServer.getIP();
				dteLastSeen = oServer.getLastSeen();
			}
		}
		return sIP;
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		if (this.getRequestedOrientation() != newConfig.orientation)
			Log.v(TAG, "(II) Screen was rotated!");

		super.onConfigurationChanged(newConfig);
	}

	@Override
	protected void onPause() {
		super.onPause();
		getCurrentView().pause();
		saveServerList();
		// Cancel connect-task if running
		if(m_tskConnect != null )
			m_tskConnect.cancel(true);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// saveServerList();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add("Refresh").setOnMenuItemClickListener(
				new OnMenuItemClickListener() {
					public boolean onMenuItemClick(MenuItem item) {
						getCurrentView().refresh();
						return true;
					}
				});

		menu.add("Server").setOnMenuItemClickListener(
				new OnMenuItemClickListener() {
					public boolean onMenuItemClick(MenuItem item) {
						requestIPAddress();
						return true;
					}
				});

		menu.add("AnDisper ver. " + AnDisperActivity.VERSION)
				.setOnMenuItemClickListener(new OnMenuItemClickListener() {
					public boolean onMenuItemClick(MenuItem item) {
						// TODO: Add about-box
						return true;
					}
				});

		return super.onCreateOptionsMenu(menu);
	}

	/*
	 * 
	 * *********** PRIVATE METHODS ************
	 */

	private void connectToServer(String p_sServerIP) {

		if (m_tskConnect == null) {
			m_tskConnect = new _ConnectToServerTask(this);
			m_tskConnect.execute(p_sServerIP);
		}
	}

	private I_UI_ViewController getCurrentView() {
		if (m_rootFlipper.getCurrentView().getId() == m_uiDisplyConfig
				.getView().getId())
			return m_uiDisplyConfig;
		else if (m_rootFlipper.getCurrentView().getId() == m_uiApplications
				.getView().getId())
			return m_uiApplications;

		return null;
	}

	/**
	 * Loads the seen servers-list from internal storage
	 */
	private void saveServerList() {
		try {
			FileOutputStream fosServerList = openFileOutput("serverList.cfg",
					Context.MODE_PRIVATE);
			OutputStream buffer = new BufferedOutputStream(fosServerList);
			ObjectOutput output = new ObjectOutputStream(buffer);
			output.writeObject(m_mapSeenServers);
			output.flush();
			output.close();

		} catch (FileNotFoundException e) {
			Log.e(TAG, "(EE) Failed to serialize seen servers!", e);
		} catch (IOException e) {
			Log.e(TAG, "(EE) Failed to serialize seen servers!", e);
		}
	}

	private void loadServerList() {
		try {
			FileInputStream fisServerList = openFileInput("serverList.cfg");
			InputStream buffer = new BufferedInputStream(fisServerList);
			ObjectInput input = new ObjectInputStream(buffer);
			@SuppressWarnings("unchecked")
			Map<String, ServerInfo> readObject = ((Map<String, ServerInfo>) input
					.readObject());
			if (readObject != null)
				m_mapSeenServers = readObject;
			else
				m_mapSeenServers = new HashMap<String, ServerInfo>();

			return;

		} catch (FileNotFoundException e) {
			Log.e(TAG, "(EE) Failed to load seen servers!", e);
		} catch (IOException e) {
			Log.e(TAG, "(EE) Failed to load seen servers!", e);
		} catch (ClassNotFoundException e) {
			Log.e(TAG, "(EE) Failed to load seen servers!", e);
		}
		m_mapSeenServers = new HashMap<String, ServerInfo>();
	}

	/*
	 * 
	 * ************ PUBLIC INTERFACE **********
	 */

	// Get the touch delegate that propagates touchevents to the ViewFlipper
	public TouchDelegate getTouchDelegate(){
		return m_oTouchDelegate;
	}
	// Shows the screen to input a new IP-address
	public void requestIPAddress() {
		if (m_oDisperServer != null && m_oDisperServer.getServerIP() != null)
			requestIPAddress(null, m_oDisperServer.getServerIP());
		else
			requestIPAddress(null, null);
	}

	public void requestIPAddress(String p_sTitleMessage) {
		if (m_oDisperServer != null && m_oDisperServer.getServerIP() != null)
			requestIPAddress(p_sTitleMessage, m_oDisperServer.getServerIP());
		else
			requestIPAddress(p_sTitleMessage, null);
	}

	// D.o, plus shows a Toast message
	public void requestIPAddress(String p_sTitleMessage, String p_sDefaultIP) {
		// Visa meddelande?
		if (p_sTitleMessage != null) {
			int duration = Toast.LENGTH_SHORT;

			Toast toast = Toast.makeText(this, p_sTitleMessage, duration);
			toast.show();
		}
		Intent intGetServer = new Intent(this, ServerSetupActivity.class);
		startActivityForResult(intGetServer, 1); // RequestCode 1 == getIP
		// finish();
	}

	/**
	 * Receive result from ServerSetup-screen
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		if (resultCode == RESULT_CANCELED)
			return; // Something went wrong
		if (requestCode == 1) { // 1 == Request IP
			String sNewIP = data.getStringExtra("IP");
			this.connectToServer(sNewIP);
		}
	}

	public DisperInterface getDisperServer() {
		return m_oDisperServer;
	}

	public void onConnectionError(String p_sMessage) {
		Log.v(TAG,
				"(II) Got a ConnectionError event. Now, show requestIP screen");
		requestIPAddress(p_sMessage);
	}

	/*
	 * 
	 * ********** TASKS ********************
	 */

	private class _ConnectToServerTask extends AsyncTask<String, String, Boolean> {
		private ProgressDialog m_uiProgress;
		private MainActivity m_actParent;
		private String m_sIP;

		_ConnectToServerTask(MainActivity p_actParent) {
			m_actParent = p_actParent;
		}

		@Override
		protected Boolean doInBackground(String... params) {
			m_sIP = params[0];
			publishProgress(params);
			if (m_oDisperServer == null)
				m_oDisperServer = new DisperInterface(
						new IconCache(m_actParent), m_sIP);
			else
				m_oDisperServer.setServerIP(m_sIP);
			if (!isCancelled()) {
				boolean bConn = m_oDisperServer.isConnected();
				Log.v(TAG, "(II) DisperServer.isconnected: " + bConn);
				return bConn;
			} else
				return false;
		}

		@Override
		protected void onProgressUpdate(String... values) {

			m_uiProgress.setMessage("Trying to connect to server at "
					+ values[0]);
			super.onProgressUpdate(values);
		}

		@Override
		protected void onCancelled() {
			super.onCancelled();
			m_uiProgress.dismiss();
			// Task finished, remove ref to it...
			m_tskConnect = null;
		}

		@Override
		protected void onPostExecute(Boolean result) {
			super.onPostExecute(result);
			m_uiProgress.dismiss();
			// Task finished, remove ref to it...
			m_tskConnect = null;
			
			if (result) {
				AsyncTask<Void, Void, String> taskGetHostInfo = new AsyncTask<Void, Void, String>(){

					@Override
					protected String doInBackground(Void... params) {
						String sHostName = m_oDisperServer.getHostname();
						return sHostName;						
					}

					@Override
					protected void onPostExecute(String p_sHostName) {
						super.onPostExecute(p_sHostName);

						if (p_sHostName!= null) {
							// Append this server to the seen ones
							ServerInfo oServer = new ServerInfo(m_sIP, p_sHostName,
									new Date());
							MainActivity.m_mapSeenServers.put(m_sIP, oServer);
							m_actParent.setTitle("Connected to " + p_sHostName);

							Toast.makeText(m_actParent,
									"Connected to " + p_sHostName + " (" + m_sIP + ")",
									Toast.LENGTH_SHORT).show();
						} else {
							m_actParent.setTitle("Not connected!");

							Toast.makeText(m_actParent,
									"Failed to connect to " + m_sIP, Toast.LENGTH_SHORT)
									.show();
						}
						getCurrentView().resume();
					}
				};
				taskGetHostInfo.execute((Void[])null);
			
			} else {
				if (m_sIP != null)
					requestIPAddress("Failed to connect to server " + m_sIP
							+ "!", m_sIP);
				else
					requestIPAddress("No server address specified!");
			}

		}

		@Override
		protected void onPreExecute() {
			// super.onPreExecute();
			m_uiProgress = ProgressDialog.show(m_actParent, "Connecting...",
					"Trying to connect to server", true);
		}

	}

	/**
	 * Listener for swipe gestures, and flips views when detected. Code from
	 * http://smartandroidians.blogspot.com/
	 * 
	 * @author http://smartandroidians.blogspot.com/
	 * 
	 */
	private class _SwipeGestureDetector extends SimpleOnGestureListener {

		private static final int SWIPE_MIN_DISTANCE = 55; // 120
		private static final int SWIPE_MAX_OFF_PATH = 150; // 250
		private static final int SWIPE_THRESHOLD_VELOCITY = 100; // 200
		private static final int ANIMATION_DURATION = 250;

		private Animation inFromRightAnimation() {
			Animation inFromRight = new TranslateAnimation(
					Animation.RELATIVE_TO_PARENT, +1.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f);
			inFromRight.setDuration(ANIMATION_DURATION);
			inFromRight.setInterpolator(new AccelerateInterpolator());
			return inFromRight;
		}

		private Animation outToLeftAnimation() {
			Animation outtoLeft = new TranslateAnimation(
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, -1.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f);
			outtoLeft.setDuration(ANIMATION_DURATION);
			outtoLeft.setInterpolator(new AccelerateInterpolator());
			return outtoLeft;
		}

		private Animation inFromLeftAnimation() {
			Animation inFromLeft = new TranslateAnimation(
					Animation.RELATIVE_TO_PARENT, -1.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f);
			inFromLeft.setDuration(ANIMATION_DURATION);
			inFromLeft.setInterpolator(new AccelerateInterpolator());
			return inFromLeft;
		}

		private Animation outToRightAnimation() {
			Animation outtoRight = new TranslateAnimation(
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, +1.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f,
					Animation.RELATIVE_TO_PARENT, 0.0f);
			outtoRight.setDuration(ANIMATION_DURATION);
			outtoRight.setInterpolator(new AccelerateInterpolator());
			return outtoRight;
		}

		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			Log.v(TAG, "(II) infling");
			if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH){
				Log.v(TAG, "(II) SWIPE_MAX_OFF_PATH Cancling fling");
				return false;
			}
			if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
					&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
				m_rootFlipper.setInAnimation(inFromRightAnimation());
				m_rootFlipper.setOutAnimation(outToLeftAnimation());
				m_rootFlipper.showNext();
				getCurrentView().resume();
				return true;

			} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
					&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
				m_rootFlipper.setInAnimation(inFromLeftAnimation());
				m_rootFlipper.setOutAnimation(outToRightAnimation());
				m_rootFlipper.showPrevious();
				getCurrentView().resume();
				return true;
			}
			Log.v(TAG, "(II) No fling....");
			return super.onFling(e1, e2, velocityX, velocityY);
		}

	}

	/**
	 * Override the ViewFlipper class to intercept and examine touch events to
	 * detect flips. This was needed as the default implementation does not seem
	 * catch touch events at all...
	 * 
	 * @author niklas
	 * 
	 */
	private class _MyFlipper extends ViewFlipper {
		private GestureDetector m_oGestureDetector;

		public _MyFlipper(Context context) {
			super(context);
			m_oGestureDetector = new GestureDetector(
					new _SwipeGestureDetector());
		}

		@Override
		public boolean onInterceptTouchEvent(MotionEvent ev) {
			if (m_oGestureDetector.onTouchEvent(ev))
				return true;
			else
				return false;
		}

	}

}
